/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.pb138.jprvyd;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.exist.xmldb.EXistResource;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.XPathQueryService;

/**
 *
 * @author tom
 */
public class InvoiceManagerImpl implements InvoiceManager {

    private String URI;
    private Database database = new org.exist.xmldb.DatabaseImpl();
    private Collection col;
    private XPathQueryService xpqs;
    private FileHandler handler;
    public static final Logger l = Logger.getLogger("InvoiceManagerImplLogger");

    public InvoiceManagerImpl() {
        this("xmldb:exist://localhost:8080/exist/xmlrpc/db/jprvyd");
    }

    public InvoiceManagerImpl(String u) {
        try {
            this.URI = u;
            handler = new FileHandler("log/log.log");
            l.addHandler(handler);

            DatabaseManager.registerDatabase(database);
            col = DatabaseManager.getCollection(URI);
            xpqs = (XPathQueryService) col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
        } catch (Exception ex) {
            l.log(Level.SEVERE, "Error establishing database connection", ex);
        }
        l.log(Level.INFO, "DB connection established");
    }

    @Override
    public void createInvoice(Invoice invoice) throws IllegalEntityException {
        l.log(Level.INFO, "InvoiceManagerImpl.createInvoice: Creating Invoice");

        if (invoice == null) {
            String info = "InvoiceManagerImpl.createInvoice: Invoice is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }
        if (invoice.getId() != null) {
            String info = "InvoiceManagerImpl.createInvoices: id is set";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getContact() == null) {
            String info = "InvoiceManagerImpl.createInvoices: contact is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getDate() == null) {
            String info = "InvoiceManagerImpl.createInvoices: date is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getEndDate() == null) {
            String info = "InvoiceManagerImpl.createInvoices: enddate is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getPaymentMethod() == null) {
            String info = "InvoiceManagerImpl.createInvoices: payment method is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getItems() == null || invoice.getItems().isEmpty()) {
            String info = "InvoiceManagerImpl.createInvoices: items are null or empty";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        try {
            Long id;
            if (this.getAllInvoices().isEmpty()) {
                id = Long.parseLong("1");
            } else {
                id = Long.parseLong(xpqs.query("max(for $x in doc(\"invoices.xml\")//invoice return $x/@id)").getIterator().nextResource().getContent().toString());
                id++;
            }
            invoice.setId(id);
            String node = getNode(invoice);

            String query = "update insert " + node + " into doc(\"invoices.xml\")//invoices";

            ResourceSet res = xpqs.query(query);

        } catch (Exception ex) {
            l.log(Level.SEVERE, "InvoiceManagerImpl.createInvoice: Exception creating Invoice: ", ex);
        }

        l.log(Level.INFO, "InvoiceManagerImpl.createInvoice: Invoice created");

    }

    @Override
    public void removeInvoice(Long id) {
        l.log(Level.INFO, "InvoiceManagerImpl.removeInvoice: Removing Invoice");

        if (id == null) {
            String info = "InvoiceManagerImpl.removeInvoice: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        try {
            String query = "for $invoice in doc(\"invoices.xml\")//invoice[@id=\"" + id + "\"] return update delete $invoice";
            xpqs.query(query);

        } catch (Exception ex) {
            l.log(Level.SEVERE, "InvoiceManagerImpl.removeInvoice: Exception removing Invoice: ", ex);
        }

        l.log(Level.INFO, "InvoiceManagerImpl.removeInvoice: Invoice removed");
    }

    @Override
    public void updateInvoice(Invoice invoice) throws IllegalEntityException {

        if (invoice == null) {
            String info = "InvoiceManagerImpl.updateInvoice: Invoice is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }
        if (invoice.getId() == null) {
            String info = "InvoiceManagerImpl.updateInvoices: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getContact() == null) {
            String info = "InvoiceManagerImpl.updateInvoices: contact is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getDate() == null) {
            String info = "InvoiceManagerImpl.updateInvoices: date is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getEndDate() == null) {
            String info = "InvoiceManagerImpl.updateInvoices: enddate is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getPaymentMethod() == null) {
            String info = "InvoiceManagerImpl.uptateInvoices: payment method is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (invoice.getItems() == null || invoice.getItems().isEmpty()) {
            String info = "InvoiceManagerImpl.uptdateInvoices: items are null or empty";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }
        try {
            String node = getNode(invoice);

            if (xpqs.query("for $x in doc(\"invoices.xml\")//invoice[@id=\"" + invoice.getId() + "\"] return $x/number").getIterator().hasMoreResources() == false) {
                throw new IllegalEntityException("InvoiceManagerImpl.updateInvoice: Invoice does not exist");
            }


            String query = "update replace doc(\"invoices.xml\")//invoice[@id=" + invoice.getId() + "] with " + node;
            xpqs.query(query);
        } catch (XMLDBException ex) {
            l.log(Level.SEVERE, "InvoiceManagerImpl.updateInvoice: Exception updating Invoice ", ex);
        }
        l.log(Level.INFO, "InvoiceManagerImpl.updateInvoice: Invoiceupdated");

    }

    private List<Item> getItems(String where) {
        List<Item> items = new ArrayList<Item>();

        try {

            String queryStart = "for $x in doc(\"invoices.xml\")//invoice where ";
            String queryEnd = " return data($x/";

            ResourceSet resultName = xpqs.query(queryStart + where + queryEnd + "items/item/name)");
            ResourceSet resultQty = xpqs.query(queryStart + where + queryEnd + "items/item/qty)");
            ResourceSet resultUnit = xpqs.query(queryStart + where + queryEnd + "items/item/unit)");
            ResourceSet resultUnitPrice = xpqs.query(queryStart + where + queryEnd + "items/item/unitprice)");
            
            ResourceIterator iterName = resultName.getIterator();
            ResourceIterator iterQty = resultQty.getIterator();
            ResourceIterator iterUnit = resultUnit.getIterator();
            ResourceIterator iterUnitPrice = resultUnitPrice.getIterator();
                        
            Resource resName = null;
            Resource resQty = null;
            Resource resUnit = null;
            Resource resUnitPrice = null;
            
                            while (iterName.hasMoreResources()) {
                    try {
                        resName = iterName.nextResource();
                        resQty = iterQty.nextResource();
                        resUnit = iterUnit.nextResource();
                        resUnitPrice = iterUnitPrice.nextResource();

                        Item item = new Item();

                        item.setName(resName.getContent().toString());
                        item.setQuantity(Integer.parseInt(resQty.getContent().toString()));
                        item.setUnit(resUnit.getContent().toString());
                        item.setUnitPrice(Integer.parseInt(resUnitPrice.getContent().toString()));
                        items.add(item);
                    } finally {
                        try {
                            ((EXistResource) resName).freeResources();
                            ((EXistResource) resQty).freeResources();
                            ((EXistResource) resUnit).freeResources();
                            ((EXistResource) resUnitPrice).freeResources();
                        } catch (Exception ex) {
                            l.log(Level.SEVERE, "InvoiceManagerImpl.getInvoiceForm: Exception occured: ", ex);

                        }
                    }
                }

        } catch (XMLDBException ex) {
            Logger.getLogger(InvoiceManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return items;
    }
    
    @Override
    public int getSumTotal() {                          
        l.log(Level.INFO, "InvoiceManagerImpl.getSumTotal");
        int sum = 0;
        try {
            sum = Integer.parseInt(xpqs.query("sum(for $x in doc(\"invoices.xml\")//invoice/items/item return data($x/qty*$x/unitprice))").getIterator().nextResource().getContent().toString());
        } catch (XMLDBException ex) {
            l.log(Level.SEVERE, "InvoiceManagerImpl.getSumTotal: could not retrieve total", ex);
        }
        return sum;
        
    }

    private Invoice getInvoiceForm(String where) {
        Invoice invoice = new Invoice();     

        try {
            String queryStart = "for $x in doc(\"invoices.xml\")//invoice where ";
            String queryEnd = " return data($x/";

            ResourceSet resultId = xpqs.query(queryStart + where + queryEnd + "@id)");
            ResourceSet resultNumber = xpqs.query(queryStart + where + queryEnd + "number)");
            ResourceSet resultContactId = xpqs.query(queryStart + where + queryEnd + "contact)");
            ResourceSet resultDateString = xpqs.query(queryStart + where + queryEnd + "date)");
            ResourceSet resultEndDateString = xpqs.query(queryStart + where + queryEnd + "enddate)");
            ResourceSet resultVariableSymbol = xpqs.query(queryStart + where + queryEnd + "variablesymbol)");
            ResourceSet resultPaymentMethodId = xpqs.query(queryStart + where + queryEnd + "paymentmethod)");

            ResourceIterator iterId = resultId.getIterator();
            ResourceIterator iterNumber = resultNumber.getIterator();
            ResourceIterator iterDateString = resultDateString.getIterator();
            ResourceIterator iterEndDateString = resultEndDateString.getIterator();
            ResourceIterator iterContactId = resultContactId.getIterator();
            ResourceIterator iterVariableSymbol = resultVariableSymbol.getIterator();
            ResourceIterator iterPaymentMethodId = resultPaymentMethodId.getIterator();

            Resource resId = null;
            Resource resNumber = null;
            Resource resDateString = null;
            Resource resEndDateString = null;
            Resource resContactId = null;
            Resource resVariableSymbol = null;
            Resource resPaymentMethodId = null;

            try {                
                resId = iterId.nextResource();
                resNumber = iterNumber.nextResource();
                resDateString = iterDateString.nextResource();
                resEndDateString = iterEndDateString.nextResource();
                resContactId = iterContactId.nextResource();
                resVariableSymbol = iterVariableSymbol.nextResource();
                resPaymentMethodId = iterPaymentMethodId.nextResource();

                DateFormat dateF = new SimpleDateFormat("yyyy-MM-dd");
                
                if(resId == null){
                    return null;
                }

                invoice.setId(Long.parseLong(resId.getContent().toString()));
                invoice.setNumber(Integer.parseInt(resNumber.getContent().toString()));
                invoice.setDate(dateF.parse(resDateString.getContent().toString()));
                invoice.setEndDate(dateF.parse(resEndDateString.getContent().toString()));

                Long id = Long.parseLong(resContactId.getContent().toString());
                ContactManagerImpl manager = new ContactManagerImpl(URI);
                Contact contact = manager.getContactById(id);

                invoice.setContact(contact);
                invoice.setVariableSymbol(Integer.parseInt(resVariableSymbol.getContent().toString()));

                PaymentMethodManagerImpl man = new PaymentMethodManagerImpl(URI);
                Long metId = Long.parseLong(resPaymentMethodId.getContent().toString());
                PaymentMethod method = man.getPaymentMethod(metId);

                invoice.setPaymentMethod(method);

                    invoice.setItems(getItems(where));
                
            } catch (Exception ex) {
                l.log(Level.SEVERE, "InvoiceManagerImpl.getInvoiceForm: Exception getting Invoice: ", ex);
                throw new Exception(ex);
            } finally {
                if(resId != null){
                    try {
                        ((EXistResource) resId).freeResources();
                        ((EXistResource) resContactId).freeResources();
                        ((EXistResource) resDateString).freeResources();
                        ((EXistResource) resEndDateString).freeResources();
                        ((EXistResource) resNumber).freeResources();
                        ((EXistResource) resPaymentMethodId).freeResources();
                        ((EXistResource) resVariableSymbol).freeResources();

                    } catch (Exception ex) {
                        l.log(Level.SEVERE, "InvoiceManagerImpl.getInvoiceForm: Exception freeing up resources: ", ex);
                    }
                }

            }

        } catch (Exception ex) {
            l.log(Level.SEVERE, "InvoiceManagerImpl.getInvoiceForm: Excepion occured: ", ex);
        } finally {

            if (col != null) {
                try {
                    col.close();
                } catch (XMLDBException ex) {
                    l.log(Level.SEVERE, "InvoiceManagerImpl.getInvoiceForm: Exception closing collection: ", ex);
                }
            }
        }
        l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceForm: Invoice retrieved");
        if (invoice.getId() == null) {
            l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceForm: No Invoice found");
            return null;
        } else {
            l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceForm: Invoice retrieved");
            return invoice;
        }

    }

    @Override
    public Invoice getInvoiceById(Long id) {
        l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceById: Retrieving Invoice");

        if (id == null) {
            String info = "InvoiceManagerImpl.getInvoiceById: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        String where = "@id=" + Long.toString(id);

        return getInvoiceForm(where);
    }

    @Override
    public Invoice getInvoiceByNumber(int number) {
        l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceByIco: Retrieving Invoice");

        Integer i = number;
        if (i == null) {
            String info = "InvoiceManagerImpl.getInvoiceByIco: ico is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        return getInvoiceForm("number = " + Integer.toString(number));
    }

    @Override
    public Invoice getInvoiceByVariableSymbol(int variableSymbol) {
        l.log(Level.INFO, "InvoiceManagerImpl.getInvoiceByIco: Retrieving Invoice");

        Integer i = variableSymbol;
        if (i == null) {
            String info = "InvoiceManagerImpl.getInvoiceByIco: ico is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        return getInvoiceForm("variablesymbol = " + Integer.toString(variableSymbol));
    }

    @Override
    public List<Invoice> getInvoicesByPaymentMethod(PaymentMethod paymentMethod) throws IllegalEntityException {

        if (paymentMethod == null) {
            String info = "InvoiceManagerImpl.getInvoiceById: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        String where = "paymentmethod=\"" + paymentMethod.getId() + "\"";

        return getInvoicesForm(where);
    }

    private List<Invoice> getInvoicesForm(String where) throws NumberFormatException {
        List<Invoice> invoices = new ArrayList<Invoice>();

        try {
            ResourceSet result = xpqs.query("for $x in doc(\"invoices.xml\")//invoice "
                    + where + " return data($x/@id)");

            ResourceIterator iter = result.getIterator();
            Resource res = null;

            while (iter.hasMoreResources()) {
                res = iter.nextResource();
                Long id = Long.parseLong(res.getContent().toString());
                invoices.add(getInvoiceById(id));
            }

        } catch (XMLDBException ex) {
            Logger.getLogger(InvoiceManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
        }

        return invoices;
    }

    @Override
    public List<Invoice> getAllInvoices() {
        return getInvoicesForm("");
    }

    @Override
    public List<Invoice> getInvoicesByContact(Contact contact) {

        if (contact == null) {
            String info = "InvoiceManagerImpl.getInvoiceByContact:contact is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        String where = "where contact=\"" + contact.getId() + "\"";

        return getInvoicesForm(where);
    }

    @Override
    public List<Invoice> getInvoicesByDate(Date date) {
        if (date == null) {
            String info = "InvoiceManagerImpl.getInvoiceByDate: date is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        DateFormat dateF = new SimpleDateFormat("yyyy-MM-dd");
        String where = "where date=\"" + dateF.format(date) + "\"";

        return getInvoicesForm(where);
    }

    @Override
    public List<Invoice> getInvoicesByGetDate(Date date) {

        if (date == null) {
            String info = "InvoiceManagerImpl.getInvoiceByGetId: date is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        DateFormat dateF = new SimpleDateFormat("yyyy-MM-dd");
        String where = "where enddate=\"" + dateF.format(date) + "\"";

        return getInvoicesForm(where);
    }

    private String getNode(Invoice invoice) {
        String node = "<invoice id=\"" + invoice.getId() + "\">"
                + "<number>" + invoice.getNumber() + "</number>"
                + "<contact>" + invoice.getContact().getId() + "</contact>"
                + "<items>";

        for (Item item : invoice.getItems()) {
            node += "<item>"
                    + "<name>" + item.getName() + "</name>"
                    + "<qty>" + item.getQuantity() + "</qty>"
                    + "<unit>" + item.getUnit() + "</unit>"
                    + "<unitprice>" + item.getUnitPrice() + "</unitprice>"
                    + "</item>";
        }
        DateFormat dateF = new SimpleDateFormat("yyyy-MM-dd");

        node += "</items>"
                + "<paymentmethod>" + invoice.getPaymentMethod().getId() + "</paymentmethod>"
                + "<date>" + dateF.format(invoice.getDate()) + "</date>"
                + "<enddate>" + dateF.format(invoice.getEndDate()) + "</enddate>"
                + "<variablesymbol>" + invoice.getVariableSymbol() + "</variablesymbol>"
                + "</invoice>";

        return node;
    }
}
